<?php

// +----------------------------------------------------------------------
// | WeChatDeveloper
// +----------------------------------------------------------------------
// | 版权所有 2014~2018 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
// +----------------------------------------------------------------------
// | 官方网站: http://think.ctolog.com
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// +----------------------------------------------------------------------
// | github开源项目：https://github.com/zoujingli/WeChatDeveloper
// +----------------------------------------------------------------------

namespace Itdashu\SwoftThirdPartyService\Service\Base;

use Itdashu\SwoftThirdPartyService\Service\WeChat\Exceptions\InvalidArgumentException;
use Itdashu\SwoftThirdPartyService\Service\WeChat\Exceptions\InvalidResponseException;
use Itdashu\SwoftThirdPartyService\Service\WeChat\Exceptions\LocalCacheException;
use Swoft\Redis\Redis;
use Swoft\Stdlib\Helper\StringHelper;

/**
 * 网络请求支持
 * Class Tools
 * @package WeChat\Contracts
 */
class Tools
{
  /**
   * 缓存路径
   * @var null
   */
  public static $cache_path;

  /**
   * 网络缓存
   * @var array
   */
  private static $cache_curl = [];

  /**
   * 产生随机字符串
   * @param int $length 指定字符长度
   * @return string
   * @throws \Exception
   */
  public static function randomString($length = 32): string
  {
    return StringHelper::randomString('alnum', $length);
  }


  /**
   * 根据文件后缀获取文件类型
   * @param string|array $ext 文件后缀
   * @param array $mine 文件后缀MINE信息
   * @return string
   */
  public static function getExtMine($ext, $mine = []): string
  {
    $mines = self::getMines();
    foreach (is_string($ext) ? explode(',', $ext) : $ext as $e) {
      $mine[] = $mines[strtolower($e)] ?? 'application/octet-stream';
    }
    return implode(',', array_unique($mine));
  }

  /**
   * 获取所有文件扩展的类型
   * @return array
   */
  private static function getMines(): array
  {
    $mines = self::getCache('all_ext_mine');
    if (empty($mines)) {
      $content = file_get_contents('http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types');
      preg_match_all('#^([\S]{2,}?)\s+(.+?)$#sm', $content, $matches, PREG_SET_ORDER);
      foreach ($matches as $match) {
        foreach (explode(' ', $match[2]) as $ext) {
          $mines[$ext] = $match[1];
        }
      }
      self::setCache('all_ext_mine', $mines);
    }
    return $mines;
  }

  /**
   * 创建CURL文件对象
   * @param $fileName
   * @param string $mimeType
   * @param string $postName
   * @return \CURLFile|string
   */
  public static function createCurlFile($fileName, $mimeType = null, $postName = null)
  {
    if (is_string($fileName) && file_exists($fileName)) {
      if ($postName === null) {
        $postName = basename($fileName);
      }
      if ($mimeType === null) {
        $mimeType = self::getExtMine(pathinfo($fileName, 4));
      }
      if (function_exists('curl_file_create')) {
        return curl_file_create($fileName, $mimeType, $postName);
      }
      return "@{$fileName};filename={$postName};type={$mimeType}";
    }
    return $fileName;
  }

  /**
   * 数组转XML内容
   * @param array $data
   * @return string
   */
  public static function arr2xml($data): string
  {
    return '<xml>' . self::_arr2xml($data) . '</xml>';
  }

  /**
   * XML内容生成
   * @param array $data 数据
   * @param string $content
   * @return string
   */
  private static function _arr2xml($data, $content = ''): string
  {
    foreach ($data as $key => $val) {
      is_numeric($key) && $key = 'item';
      $content .= "<{$key}>";
      if (is_array($val) || is_object($val)) {
        $content .= self::_arr2xml($val);
      } elseif (is_string($val)) {
        $content .= '<![CDATA[' . preg_replace("/[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]/", '', $val) . ']]>';
      } else {
        $content .= $val;
      }
      $content .= "</{$key}>";
    }
    return $content;
  }

  /**
   * 解析XML内容到数组
   * @param string $xml
   * @return array
   */
  public static function xml2arr($xml): array
  {
    $entity = libxml_disable_entity_loader();
    $data = (array)simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
    libxml_disable_entity_loader($entity);
    return json_decode(json_encode($data), true);
  }

  /**
   * 数组转xml内容
   * @param array $data
   * @return null|string|string
   */
  public static function arr2json($data): ?string
  {
    $json = json_encode(self::buildEnEmojiData($data), JSON_UNESCAPED_UNICODE);
    return $json === '[]' ? '{}' : $json;
  }

  /**
   * 数组对象Emoji编译处理
   * @param array $data
   * @return array
   */
  public static function buildEnEmojiData(array $data): array
  {
    foreach ($data as $key => $value) {
      if (is_array($value)) {
        $data[$key] = self::buildEnEmojiData($value);
      } elseif (is_string($value)) {
        $data[$key] = self::emojiEncode($value);
      } else {
        $data[$key] = $value;
      }
    }
    return $data;
  }

  /**
   * 数组对象Emoji反解析处理
   * @param array $data
   * @return array
   */
  public static function buildDeEmojiData(array $data): array
  {
    foreach ($data as $key => $value) {
      if (is_array($value)) {
        $data[$key] = self::buildDeEmojiData($value);
      } elseif (is_string($value)) {
        $data[$key] = self::emojiDecode($value);
      } else {
        $data[$key] = $value;
      }
    }
    return $data;
  }

  /**
   * Emoji原形转换为String
   * @param $content
   * @return mixed
   */
  public static function emojiEncode($content)
  {
    return json_decode(preg_replace_callback("/(\\\u[ed][0-9a-f]{3})/i", self::class . '::emojiEncodeReplace',
      json_encode($content, true)), true);
  }

  /**
   * @param $string
   * @return string
   */
  public static function emojiEncodeReplace($string): string
  {
    return addslashes($string[0]);
  }

  /**
   * Emoji字符串转换为原形
   * @param $content
   * @return mixed
   */
  public static function emojiDecode($content)
  {
    return json_decode(
      preg_replace_callback('/\\\\\\\\/', self::class . '::emojiDecodeReplace',
        json_encode($content)
      ),
      true);
  }

  public static function emojiDecodeReplace(): string
  {
    return '\\';
  }

  /**
   * 解析JSON内容到数组
   * @param string $json
   * @return array
   * @throws InvalidResponseException
   */
  public static function jsonDecode($json): array
  {
    $result = json_decode($json, true);
    if (empty($result)) {
      throw new InvalidResponseException('invalid response.', '0');
    }
    if (!empty($result['errcode'])) {
      throw new InvalidResponseException($result['errmsg'], $result['errcode'], $result);
    }
    return $result;
  }

  /**
   * 以get访问模拟访问
   * @param string $url 访问URL
   * @param array $query GET数
   * @param array $options
   * @return mixed
   * @throws LocalCacheException
   */
  public static function get($url, $query = [], $options = [])
  {
    $options['query'] = $query;
    return self::doRequest('get', $url, $options);
  }

  /**
   * 以post访问模拟访问
   * @param string $url 访问URL
   * @param array $data POST数据
   * @param array $options
   * @return mixed
   * @throws LocalCacheException
   */
  public static function post($url, $data = [], $options = [])
  {
    $options['data'] = $data;
    return self::doRequest('post', $url, $options);
  }

  /**
   * CURL模拟网络请求
   * @param string $method 请求方法
   * @param string $url 请求方法
   * @param array $options 请求参数[headers,data,ssl_cer,ssl_key]
   * @return mixed
   * @throws LocalCacheException
   */
  public static function doRequest($method, $url, $options = [])
  {
    $curl = curl_init();
    // GET参数设置
    if (!empty($options['query'])) {
      $url .= (strpos($url, '?') !== false ? '&' : '?') . http_build_query($options['query']);
    }
    // CURL头信息设置
    if (!empty($options['headers'])) {
      curl_setopt($curl, CURLOPT_HTTPHEADER, $options['headers']);
    }
    // POST数据设置
    if (strtolower($method) === 'post') {
      curl_setopt($curl, CURLOPT_POST, true);
      curl_setopt($curl, CURLOPT_POSTFIELDS, self::_buildHttpData($options['data']));
    }
    // 证书文件设置
    if (!empty($options['ssl_cer'])) {
      if (file_exists($options['ssl_cer'])) {
        curl_setopt($curl, CURLOPT_SSLCERTTYPE, 'PEM');
        curl_setopt($curl, CURLOPT_SSLCERT, $options['ssl_cer']);
      } else {
        throw new InvalidArgumentException('Certificate files that do not exist. --- [ssl_cer]');
      }
    }
    // 证书文件设置
    if (!empty($options['ssl_key'])) {
      if (file_exists($options['ssl_key'])) {
        curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM');
        curl_setopt($curl, CURLOPT_SSLKEY, $options['ssl_key']);
      } else {
        throw new InvalidArgumentException('Certificate files that do not exist. --- [ssl_key]');
      }
    }
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_TIMEOUT, 60);
    curl_setopt($curl, CURLOPT_HEADER, false);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
//    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
//    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
    [$content] = [curl_exec($curl), curl_close($curl)];
    // 清理 CURL 缓存文件
    if (!empty(self::$cache_curl)) {
      foreach (self::$cache_curl as $key => $file) {
        self::delCache($file);
        unset(self::$cache_curl[$key]);
      }
    }
    return $content;
  }

  /**
   * POST数据过滤处理
   * @param mixed $data 需要处理的数据
   * @param boolean $build 是否编译数据
   * @return array|string
   * @throws LocalCacheException
   */
  private static function _buildHttpData($data, $build = true)
  {
    if (!is_array($data)) {
      return $data;
    }
    foreach ($data as $key => $value) {
      if (is_object($value)) {
        if (isset($value->datatype) && $value->datatype === 'MY_CURL_FILE') {
          $build = false;
          $myCurl = new MyCurlFile((array)$value);
          $data[$key] = $myCurl->get();
          self::$cache_curl[] = $myCurl->tempname;
        }
        if ($value instanceof \CURLFile) {
          $build = false;
        }
      } elseif (is_string($value) && class_exists('CURLFile', false) && strpos($value, '@') === 0) {
        if (($fileName = realpath(trim($value, '@'))) && file_exists($fileName)) {
          $build = false;
          $data[$key] = self::createCurlFile($fileName);
        }
      }
    }
    return $build ? http_build_query($data) : $data;
  }

  /**
   * 写入文件
   * @param string $name 文件名称
   * @param string $content 文件内容
   * @return string
   * @throws LocalCacheException
   */
  public static function pushFile($name, $content): string
  {
    if (self::$cache_path === null) {
      $dir = sys_get_temp_dir();
    } else {
      $dir = self::$cache_path;
    }
    $filePath = $dir . '/' . $name;
    if (!file_put_contents($filePath, $content)) {
      throw new LocalCacheException('文件写入错误：' . $filePath, '0');
    }
    return $filePath;
  }

  /**
   * 缓存配置与存储
   * @param string $name 缓存名称
   * @param string $value 缓存内容
   * @param int $expired 缓存时间(0表示永久缓存)
   * @return bool
   */
  public static function setCache($name, $value = '', $expired = 3600): bool
  {
    return Redis::set($name, $value, $expired);
  }

  /**
   * @param $value
   * @return mixed
   */
  public static function cacheDecode($value)
  {
    return json_decode($value, true) ?? $value;
  }

  /**
   * @param $value
   * @return false|string
   */
  public static function cacheEncode($value)
  {
    if (is_array($value) || is_object($value)) {
      $value = json_encode($value);
    }
    return $value;
  }

  /**
   * 获取缓存内容
   * @param string $name 缓存名称
   * @return null|mixed
   */
  public static function getCache($name)
  {
    return Redis::get($name);
  }

  /**
   * 移除缓存文件
   * @param string $name 缓存名称
   * @return boolean
   */
  public static function delCache($name): bool
  {
    return Redis::del($name);
  }
}